#include "findduplicates.hpp"

#include "window.hpp"
#include <map>
#include <cstdlib>

using namespace std;

typedef QList<QList<QFileInfo *> > listinha;

bool diff(QString a,QString b){
	QFile qf1(a);
	QFile qf2(b);

	qf1.open(QIODevice::ReadOnly);
	qf2.open(QIODevice::ReadOnly);
	bool different=false;
	qint64 size=1024;
	while(true){
		QByteArray qba1=qf1.read(size);
		QByteArray qba2=qf2.read(size);
		if(qba1.isEmpty() || qba2.isEmpty()){
			if(qba1.isEmpty() && qba2.isEmpty()){
				break;
			}
			different=true;
			break;
		}
		if(qba1!=qba2){
			different=true;
			break;
		}
	}
	return different;
}

FindDuplicates::FindDuplicates(FilesList *files,QWaitCondition *wc,QMutex *mut_ui,QMutex *mut_fl,QSemaphore *sem,QWidget *parent=0):QThread((QObject*)(parent)),files(files),wc(wc),mut_ui(mut_ui),mut_fl(mut_fl),sem(sem){
	this->ff=false;
	this->terminated=false;
}

FindDuplicates::~FindDuplicates(){
}

void FindDuplicates::run(){
	switch(option){
		case 0:
			byName();
		break;
		case 1:
			bySuffix();
		break;
		case 2:
			byAll();
		break;
	}
}

void FindDuplicates::filesFinished(){
	this->ff=true;
}

void FindDuplicates::stop(){
	files->stop();
	this->terminated=true;
}

void FindDuplicates::byName(){
	typedef map<pair<QString,qint64>,listinha> mapinha;
	files->start();
	mapinha m;
	//percorre a lista files, construindo um mapa mapeando o nome do arquivo e seu tamanho em uma lista de arquivos iguais.
	while((!ff || sem->available()>0) && !terminated){
		if(!sem->tryAcquire(1,100))
			continue;
		mut_fl->lock();
		QFileInfo *file=files->takeFirst();
		mut_fl->unlock();
		QString name=file->fileName();
		qint64 size=file->size();
		pair<QString,qint64> p(name,size);
		if(m.find(p)==m.end()){
			m[p]=listinha();
		}
		bool inserted=false;
		for(listinha::iterator it2=m[p].begin();it2!=m[p].end() && !terminated;it2++){
			if(!diff((*(it2->begin()))->absoluteFilePath(),file->absoluteFilePath())){
				mut_ui->lock();
				it2->push_back(file);
				inserted=true;
				emit duplicate(&(*it2));
				wc->wait(mut_ui);
				mut_ui->unlock();
				break;
			}
		}
		if(!inserted){
			QList<QFileInfo *> ql=QList<QFileInfo *>();
			ql.push_back(file);
			m[p].push_back(ql);
		}
	}
}

void FindDuplicates::bySuffix(){
	typedef map<pair<QString,qint64>,listinha> mapinha;
	files->start();
	mapinha m;
	//percorre a lista files, construindo um mapa mapeando o nome do arquivo e seu tamanho em uma lista de arquivos iguais.
	while(!ff || sem->available()>0){
		if(!sem->tryAcquire(1,100))
			continue;
		mut_fl->lock();
		QFileInfo *file=files->takeFirst();
		mut_fl->unlock();
		QString suffix=file->completeSuffix();
		qint64 size=file->size();
		pair<QString,qint64> p(suffix,size);
		if(m.find(p)==m.end()){
			m[p]=listinha();
		}
		bool inserted=false;
		for(listinha::iterator it2=m[p].begin();it2!=m[p].end();it2++){
			if(!diff((*(it2->begin()))->absoluteFilePath(),file->absoluteFilePath())){
				mut_ui->lock();
				it2->push_back(file);
				inserted=true;
				emit duplicate(&(*it2));
				wc->wait(mut_ui);
				mut_ui->unlock();
				break;
			}
		}
		if(!inserted){
			QList<QFileInfo *> ql=QList<QFileInfo *>();
			ql.push_back(file);
			m[p].push_back(ql);
		}
	}
}

void FindDuplicates::byAll(){
	typedef map<qint64,listinha> mapinha;
	files->start();
	mapinha m;
	//percorre a lista files, construindo um mapa mapeando o nome do arquivo e seu tamanho em uma lista de arquivos iguais.
	while(!ff || sem->available()>0){
		if(!sem->tryAcquire(1,100))
			continue;
		mut_fl->lock();
		QFileInfo *file=files->takeFirst();
		mut_fl->unlock();
		qint64 size=file->size();
		if(m.find(size)==m.end()){
			m[size]=listinha();
		}
		bool inserted=false;
		for(listinha::iterator it2=m[size].begin();it2!=m[size].end();it2++){
			if(!diff((*(it2->begin()))->absoluteFilePath(),file->absoluteFilePath())){
				mut_ui->lock();
				it2->push_back(file);
				inserted=true;
				emit duplicate(&(*it2));
				wc->wait(mut_ui);
				mut_ui->unlock();
				break;
			}
		}
		if(!inserted){
			QList<QFileInfo *> ql=QList<QFileInfo *>();
			ql.push_back(file);
			m[size].push_back(ql);
		}
	}
}

void FindDuplicates::setOption(int option){
	this->option=option;
}
